home *** CD-ROM | disk | FTP | other *** search
/ NetNews Offline 2 / NetNews Offline Volume 2.iso / news / comp / lang / c++-part2 / 15172 < prev    next >
Encoding:
Text File  |  1996-08-05  |  17.4 KB  |  607 lines

  1. Newsgroups: comp.lang.ada,comp.lang.c++
  2. Path: alexandria.organon.com!alexandria!jsa
  3. From: jsa@organon.com (Jon S Anthony)
  4. Subject: Re: some questions re. Ada/GNAT from a C++/GCC user
  5. In-Reply-To: rogoff@sccm.Stanford.EDU's message of 29 Mar 96 16:30:36
  6. Message-ID: <JSA.96Apr3195057@organon.com>
  7. Sender: news@organon.com (news)
  8. Organization: Organon Motives, Inc.
  9. References: <wnewmanDoxrCp.DKv@netcom.com> <Dp1oAw.7Cz@world.std.com>
  10.     <ROGOFF.96Mar29163036@sccm.Stanford.EDU>
  11. Date: Thu, 4 Apr 1996 00:50:57 GMT
  12.  
  13. In article <ROGOFF.96Mar29163036@sccm.Stanford.EDU> rogoff@sccm.Stanford.EDU (Brian Rogoff) writes:
  14.  
  15. >    > Is there any way in Ada to iterate abstractly over the contents of a 
  16. >    > container,...
  17. >    Several ways:
  18. > [typical stuff for doing this...]
  19. >
  20. > Jon Anthony, where are you? :-)
  21. > (FYI, Jon has a very nice approach to simulating Sather iters in Ada
  22. > 95. I'll leave it for him to post.)
  23.  
  24. Thanks Brian!
  25.  
  26. OK, this has come up before and I've sent it out to various folk who
  27. were interested, but I will just append it to this post.  It was done
  28. around a year or so ago.  Note that the approach is reasonably neat
  29. (IMO, of course!), but suffers some drawbacks vis-a-vis the real thing
  30. in Sather.  In particular, an exception is involved (though it looks
  31. like 3.04 Gnat is an example going in the right direction for making
  32. simple cases very efficient) and you have to build the implementations
  33. at a lower level (you joe programmer build the state machines for the
  34. iterators...) - the Sather compiler does this dross work for you.
  35.  
  36. >    None of these is as pretty as the Sather solution, but they achieve the
  37. >    main goal, which is to have the module that defines the data structure
  38. >    define how to loop, and the clients define what they do at each
  39. >    iteration.
  40. > The Sather iteration abstraction is great, and should be considered for 
  41. > inclusion in future variants of Ada. Maybe now that GNAT exists, someone 
  42. > will make an iter enhanced Ada like language (Sada? Sadie?).
  43.  
  44. Yes, the Sather stuff is really quite excellent.  In fact, in general
  45. Sather is quite excellent.  Re new lang: Why not Sade? ;-)
  46.  
  47.  
  48. >    - Bob
  49. >    P.S. I hope you enjoy Ada.  Why not get a copy of GNAT, and try it out?
  50. > Seconded!
  51. > -- Brian
  52.  
  53. Three-peted!
  54.  
  55. --------------Sather iterators in Ada95-------------------
  56.  
  57. Hi,
  58.  
  59. I've been looking into Sather quite a lot recently (it is a very good
  60. language design) and in particular Sather iterators.  These have been
  61. discussed a bit in comp.object and comp.lang.ada (as well as
  62. comp.lang.sather!), so you may be a bit familiar with them.  Anyway, I
  63. also retrieved and read over the technical report TR-93-045 (very good
  64. paper from the Sather home page) and while reading this it occured to
  65. me that Sather iterators were very close to a kind of special case use
  66. of Ada (all references are to Ada95) protected types. Conceptually,
  67. Sather iterators work as follows:
  68.  
  69. Sather has a single looping construct: loop stmt_seq end.  It also
  70. allows for "classes" to have special operations called iterators which
  71. are pretty much like any other Sather routine except
  72.  
  73. a) They are indicated by a "!" in their declaration: my_iter! (...) is ...end
  74.  
  75. b) They may only be called from within a loop statement
  76.  
  77. c) Any iterator in a loop is implicitly declared and initialized at
  78.    the beginning of the loop and only exists for the life of the loop.
  79.  
  80. d) They may "yield" as well as "quit" (return...)
  81.  
  82. e) Between calls they *maintain state* (when yielding)
  83.  
  84. f) when any one quits, the loop is exited.
  85.  
  86.  
  87. The canonical implementation would be coroutines (where a loop is also
  88. the "main" coroutine which controls the order of "calls").  At this
  89. point it occured to me that you could sort of do this with tasks but
  90. they are much too expensive for a simple efficient iteration scheme
  91. and entries don't allow for composing - they can't return values.
  92. Then it occured to me that Sather iterators are really simple state
  93. machines and that this would be an efficient implementation.  Of
  94. course, at the end of the paper the authors also point this out and my
  95. guess is that the Sather compiler implements them as such.
  96.  
  97. The paper also points out many of the problems of the cursors/riders,
  98. streams, and blocks/lambda-expressions style of iterators.  Some of
  99. these problems do not exist with these constructs in Ada.  For
  100. example, in Ada (thanks to separation of class and module!), cursors
  101. do not "require maintaining a parallel cursor object hierarchy
  102. alongside each container class hierarchy".  However, many of the
  103. problems mentioned are equally true of such iteration techniques in
  104. Ada as well.  In general, Sather iterators do not have any of the
  105. problems mentioned and hence are indeed a very clever and well thought
  106. out scheme for general iteration.
  107.  
  108. What I am proposing here is a canonical idiom to code Sather iterators
  109. in Ada using protected types and access discriminants.  The protected
  110. types encapsulate iter operations and the access discriminants allow
  111. them to
  112.  
  113. a) have and maintain protected state (between calls),
  114. b) be implicitly initialized when declared,
  115. c) force them to operate on a particular instance of a "class" and
  116. d) "disappear" at the end of a loop decl block.
  117.  
  118. One other nice aspect is that since protected types are used, your
  119. iterators are tasking safe for "free".  At present this does not
  120. appear to be true of Sather iterators (but that is OK since Sather is
  121. not multithreaded).
  122.  
  123. I will also show a few examples, which are direct cribs of the Sather
  124. iterators presented in the TR paper.  All of this compiles and
  125. executes just fine with Gnat.
  126.  
  127.  
  128. Let's start with an encapsulated resource which can benefit from
  129. iteration:
  130.  
  131.  
  132. package Obj_Class is
  133.     type element_type is ...;  -- The elements of Obj_Type...
  134.     type Obj_Type is ...;      -- Irrelevant what it is...
  135.     ...                        -- Primitive operations for Obj_Type
  136. end Obj_Class;
  137.  
  138.  
  139. We could place all the iterator stuff in Obj_Class, but maybe it makes
  140. more sense to put them in a child package (then again, maybe not...),
  141. but you are free to do whatever makes the most sense for a given
  142. situation.  Also, these packages could be generic for added
  143. flexibility.
  144.  
  145.  
  146. package Obj_Class.Iterators is
  147.  
  148.     Quit : exception;
  149.  
  150.     type State_Type is (Yield, Done);
  151.  
  152.     type Iter_State_Type ( Obj : access Obj_Type ) is tagged limited record
  153.     State   : State_Type := Yield;
  154.     Loc     : Location_type := first_loc_function_for_obj_class;
  155.     end record;
  156.  
  157.  
  158.     type Iterator;
  159.  
  160.     protected type Iter_Op_Type ( Iter : access Iterator ) is
  161.     -- Various iteration operations...
  162.     end Iter_Update_Type;
  163.  
  164.     type Iterator is new Iter_State_Type with record
  165.     Res : Element_Type := null_element;
  166.     Op  : Iter_Op_Type(Iterator'Access);
  167.     end record;
  168.  
  169. end Obj_Class.Iterators;
  170.  
  171.  
  172. A few notes on this.  First, we could have Iter_State_Type be
  173. parameterized by Obj_Type'Class for added flexibility - any
  174. descendents of Obj_Type could use these iterators.
  175.  
  176. Second, we can have different iterators for each sort of iteration
  177. operation: reading, updating - whatever.  You would then have, say,
  178. Read_Iterator/Iter_Read_Type and Update_Iterator/Iter_Update_Type (in
  179. place of Iterator/Iter_Op_type).  This seems like a better idea than
  180. just dumping all possible iteration operations in one iterator type,
  181. but it probably depends on the situation.
  182.  
  183. Third, the reason why we have an extra separate enclosing record,
  184. Iter_State_Type, instead of using the data region of a protected type,
  185. is to allow for the iteration operations to be functions.  Function
  186. operations of protected types (for very good real time reasons!)
  187. cannot update any internal state of the type, but iterators will
  188. typically need to do this.
  189.  
  190. Fourth, the initial values for State, Loc, and Res, guarantee proper
  191. implicit initialization at the point an instance of Iterator is
  192. declared.
  193.  
  194. The body for the above will look something like:
  195.  
  196.  
  197. package body Obj_Class.Iterators is
  198.  
  199.     procedure Forward ( Iter : access Iter_State_Type'Class ) is
  200.     begin
  201.     if not At_End(Iter.Loc) then
  202.         Iter.Loc := Next_Iter_Loc(Iter.Loc);
  203.     else
  204.         Iter.State := Done;
  205.     end if;
  206.     end Forward;
  207.     pragma Inline(Forward);
  208.  
  209.  
  210.     protected body Iter_Op_Type is
  211.  
  212.     procedure Op_1 (...) is
  213.     Begin
  214.         case Iter.State is
  215.         when Yield =>
  216.             -- action on/with Iter.Obj at location Iter.Loc
  217.             Forward(Iter);
  218.         when Done =>
  219.             raise Quit;
  220.         end case;
  221.     end Op_1;
  222.  
  223.         function Op_2 ( e : Element_Type ) return Element_Type is
  224.         Begin
  225.             Case Iter.State Is
  226.                 when Yield =>
  227.                     Iter.Res := do_something(Iter.Res, e);
  228.                     return Iter.Res;
  229.                 when Done =>
  230.                     raise Quit;
  231.             end case;
  232.         end Op_2;
  233.  
  234.         ... -- Other ops for this iteration type
  235.  
  236.     END iter_update_type;
  237.  
  238. END Obj_Class.Iterators;
  239.  
  240.  
  241. Note that the particular Obj (of Iter.Obj) is given when an instance
  242. of the Iterator is declared.  Also note that all the Iter.* entities
  243. maintain their state _between calls_ to Op_1, Op_2, etc. for _each_
  244. particular instance of the iterator.
  245.  
  246.  
  247. With these resources a user can then use them to do general iteration
  248. like that available in Sather.  For example:
  249.  
  250.  
  251. with Obj_Class.Iterators;  use Obj_Class.Iterators;
  252. with Whack_It;
  253. procedure test is
  254.  
  255.     Obj_1 : Obj_Type := new Obj_Type'(...);
  256.  
  257. begin
  258.  
  259.     declare
  260.     Obj_2  : aliased Obj_Type := (others => ...);
  261.  
  262.     -- Declare an iterator for Obj_1 and Obj_2 and initialize them
  263.     --
  264.     Iter_1 : Iterator(Obj_1'Access);
  265.     Iter_2 : Iterator(Obj_2'Access);
  266.     begin
  267.     loop
  268.         -- Each time through the loop we are able to twiddle
  269.         -- Obj_1 via iterator Op_1 with the results of Obj_2
  270.         -- via iterator Op_2.
  271.         --
  272.         Iter_1.Op.Op_1(Whack_It(Iter_2.Op.Op_2));
  273.     end loop;
  274.     exception
  275.     when Quit => null;
  276.     end;
  277.  
  278.     -- Iterators are deallocated and gone, thus preserving
  279.     -- various semantic integrity aspects per Sather TR 3.2
  280.     -- and 3.3
  281.  
  282. end;
  283.  
  284.  
  285. Notice that like Sather iterators (and unlike cursors) these protected
  286. type iterators (PT iters) are part of the container class itself; PT
  287. iters mangage their own storage and in the example they use the stack
  288. and not the heap; the state of PT iters is confined to a single loop
  289. (though this _can_ be violated); and PT iters may be "arbitrarily"
  290. nested and support recursion (an example of which follows).
  291.  
  292. As noted, there is a hole in this in that unlike Sather iters, PT
  293. iters can be "passed around in a half-consumed state".  This can
  294. happen if the iters are declared more global than the loop that will
  295. use them.  If the style of the above example is used this can never
  296. happen (of course, this is not as good as the Sather iter
  297. _guarantee_...)
  298.  
  299. The other obvious question mark is that Quit exception.  While this
  300. could (reasonably easily) be optimized away into a simple exit for the
  301. loop, I doubt that all compilers would support this level of "quality
  302. of service" in this respect.  Unless, of course, this becomes a
  303. popular idiom! :-) But, in general, I think for most cases it will be
  304. quite efficient enough (exceptions can be handled _very_ quickly if
  305. caught by the inner most enclosing block as they do not need to unwind
  306. and do all sorts of other nasty things...)  Beyond any efficiency
  307. question there is the hole that the user must remember to catch the
  308. thing in loop block!  Again, in Sather this is just part of the
  309. semantics of iterators and is a non-issue.
  310.  
  311.  
  312. OK, here are some real live working examples.  Both crib examples from
  313. the TR paper and are pretty self explanatory (note that the so called
  314. "same fringe problem" is also easily handled with PT iters).
  315.  
  316. The Sieve of Eratosthenes example requires a little discussion,
  317. especially if you are familiar with the Sather version.  In Sather,
  318. recursive calls of an iterator create new _invocation_ instances
  319. (conceptually they create a new instance of the coroutine for the
  320. iterator) all of which are able to maintain their own private state.
  321. There is no simple way to do this using only PT iters (you would need
  322. to invoke the god of tasking).  Hence, for the Ada PT version, the
  323. state for the divisor "gauntlet" is made an explicit part of the
  324. declaration of the iter instance (the d field in prime_generator)
  325. while in the Sather version it is implicit in all the created
  326. invocation instances (which is definitely a nifty trick).
  327.  
  328. ------- Start Examples: all work with Gnat 2.03 on Sparc.SunOS ----------
  329.  
  330. package P is
  331. --
  332. -- Check out protected types as Sather Iterators.
  333.     
  334.     subtype Element_Type is Integer;
  335.  
  336.  
  337.     subtype Range_Type is Integer range 1..10;
  338.     type Obj_Type is array (Range_Type) of Element_Type;
  339.  
  340.  
  341.  
  342.     Quit : exception;
  343.  
  344.     type State_Type is (Yield, Done);
  345.  
  346.     type Iter_State_Type ( Obj : access Obj_Type ) is tagged limited record
  347.     State   : State_Type := Yield;
  348.     Loc     : Range_Type := Range_Type'First;
  349.     end record;
  350.  
  351.  
  352.     type Update_Iterator;
  353.  
  354.     protected type Iter_Update_Type ( Iter : access Update_Iterator ) is
  355.     procedure Set_Elts ( E: Element_Type );
  356.         function  Sum ( Summand : Element_Type ) return Element_Type;
  357.     end Iter_Update_Type;
  358.  
  359.     type Update_Iterator is new Iter_State_Type with record
  360.     Res : Element_Type := 0;
  361.     Op  : Iter_Update_Type(Update_Iterator'Access);
  362.     end record;
  363.  
  364.  
  365.     protected type Iter_Read_Type ( Iter : access Iter_State_Type'Class ) is
  366.     function  Elts return Element_Type;
  367.     end Iter_Read_Type;
  368.  
  369.     type Read_Iterator is new Iter_State_Type with record
  370.     Op  : Iter_Read_Type(Read_Iterator'Access);
  371.     end record;
  372.  
  373.  
  374. end P;
  375.  
  376.  
  377. package body P is
  378. --
  379. -- Check out protected types as Sather Iterators.
  380.  
  381.  
  382.     procedure Forward ( Iter : access Iter_State_Type'Class ) is
  383.     begin
  384.     if Iter.Loc < Range_Type'Last then
  385.         Iter.Loc := Iter.Loc + 1;
  386.     else
  387.         Iter.State := Done;
  388.     end if;
  389.     end Forward;
  390.     pragma Inline(Forward);
  391.  
  392.  
  393.  
  394.     protected body Iter_Update_Type is
  395.  
  396.     procedure Set_Elts ( E: Element_Type ) is 
  397.     begin
  398.         case Iter.State is
  399.         when Yield =>
  400.             Iter.Obj(Iter.Loc) := E;
  401.             Forward(Iter);
  402.         when Done =>
  403.             raise Quit;
  404.         end case;
  405.     end Set_Elts;
  406.  
  407.  
  408.     function  Sum ( Summand : Element_Type ) return Element_Type is 
  409.     begin
  410.         case Iter.State is
  411.         when Yield =>
  412.             Iter.Res := Iter.Res + Summand;
  413.             return Iter.Res;
  414.         when Done =>
  415.             raise Quit;
  416.         end case;
  417.     end Sum;
  418.  
  419.     end Iter_Update_Type;
  420.  
  421.  
  422.  
  423.     protected body Iter_Read_Type is
  424.  
  425.     function Elts return Element_Type is
  426.         E : Element_Type;
  427.     begin
  428.         case Iter.State is
  429.         when Yield =>
  430.             E := Iter.Obj(Iter.Loc);
  431.             Forward(Iter);
  432.             return E;
  433.         when Done =>
  434.             raise Quit;
  435.         end case;
  436.     end Elts;
  437.  
  438.     end Iter_Read_Type;
  439.  
  440.  
  441. end P;
  442.  
  443.  
  444. with Text_Io;
  445. with P;  use P;
  446.  
  447. procedure Test_New_Iters is
  448. --
  449. -- Test Sather style iterators in Ada95
  450.  
  451.     
  452.     A     : aliased Obj_Type := (others => 1);
  453.     B     : aliased Obj_Type := (1, 2, 3, 4, 5, 6, 7, 8, others => 9);
  454.     Ten_A : Obj_Type := (10, 20, 30, 40, 50, 60, 70, 80, others => 90);
  455.  
  456.     Overall_Passed : Boolean := True;
  457.  
  458.     procedure Failed ( Code : Integer ) is
  459.     begin
  460.     Text_Io.Put_Line("***FAILED test " & Integer'Image(Code));
  461.     Overall_Passed := False;
  462.     end Failed;
  463.  
  464.  
  465. begin
  466.  
  467.     declare
  468.     Au_Iter : Update_Iterator(A'Access);
  469.     Ar_Iter : Read_Iterator(A'Access);
  470.     Br_Iter : Read_Iterator(B'Access);
  471.     begin
  472.     loop
  473.         -- Standard matrix multiplication by scalar: A := B*i
  474.         --
  475.         Au_Iter.Op.Set_Elts(Br_Iter.Op.Elts * 10);
  476.          Text_Io.Put_Line(Integer'Image(Ar_Iter.Op.Elts));
  477.  
  478.     end loop;
  479.     exception
  480.     when Quit =>
  481.         if A /= Ten_A then
  482.         Failed(1);
  483.         end if;
  484.     end;
  485.  
  486.  
  487.     declare
  488.     Au_Iter : Update_Iterator(A'Access);
  489.     Ar_Iter : Read_Iterator(A'Access);
  490.     Br_Iter : Read_Iterator(B'Access);
  491.     X       : Element_Type := 0;
  492.     begin
  493.     loop
  494.         -- Compute the sum of the products of the elements of A & B
  495.         --
  496.         X := Au_Iter.Op.Sum(Ar_Iter.Op.Elts * Br_Iter.Op.Elts);
  497.  
  498.     end loop;
  499.     exception
  500.     when Quit =>
  501.          Text_Io.Put_Line("sum A(i)*B(i) =" & Integer'Image(X));
  502.         if X /= 3660 then
  503.         Failed(2);
  504.         end if;
  505.     end;
  506.  
  507.     if Overall_Passed then
  508.         Text_Io.Put_Line("PASSED");
  509.     end if;
  510.  
  511. end Test_New_Iters;
  512.  
  513.  
  514. -----------------Start Sieve Example-------------------
  515.  
  516. with Text_Io;
  517.  
  518. procedure Primes is
  519. --
  520. -- Sieve of Eratosthenes using Sather like iterators...
  521.  
  522.  
  523.     type Prime_Generator;
  524.  
  525.     protected type Siever ( Iter : access Prime_Generator ) is
  526.     function Sieve ( Aprime : Positive; I : Positive ) return Boolean;
  527.     function Gen return Positive;
  528.     end Siever;
  529.  
  530.     type Divisors is array (Positive range <>) of Natural;
  531.     type Prime_Generator (Count : Positive) is limited record
  532.     D   : Divisors(1..Count) := (others => 0);
  533.     Res : Positive := 1;
  534.     Op  : Siever(Prime_Generator'Access);
  535.     end record;
  536.  
  537.  
  538.     Quit : exception;
  539.  
  540.     protected body Siever is
  541.  
  542.     function Sieve ( Aprime : Positive; I : Positive ) return Boolean is
  543.     begin
  544.         if Iter.D(I) = 0 then
  545.         Iter.D(I) := Aprime;
  546.         return True;
  547.         elsif Aprime mod Iter.D(I) = 0 then
  548.         return False;
  549.         else
  550.         return Sieve(Aprime, I+1);
  551.         end if;
  552.     end Sieve;
  553.  
  554.     function Gen return Positive is
  555.     begin
  556.         Iter.Res := Iter.Res + 1;
  557.         if Iter.D(Iter.Count) /= 0 then
  558.         raise Quit;
  559.         elsif Sieve(Iter.Res, 1) then
  560.         return Iter.Res;
  561.         else
  562.         return Gen;
  563.         end if;
  564.     end Gen;
  565.         
  566.     end Siever;
  567.  
  568.  
  569. begin
  570.  
  571.     declare
  572.     Primes1 : Prime_Generator(10);
  573.     begin
  574.     loop
  575.         Text_Io.Put_Line(Integer'Image(Primes1.Op.Gen));
  576.     end loop;
  577.     exception
  578.     when Quit => null;
  579.     end;
  580.  
  581.  
  582.     declare
  583.     Primes1 : Prime_Generator(50);
  584.     begin
  585.     loop
  586.         Text_Io.Put_Line(Integer'Image(Primes1.Op.Gen));
  587.     end loop;
  588.     exception
  589.     when Quit => null;
  590.     end;
  591.  
  592. end Primes;
  593. -- 
  594. Jon Anthony
  595. Organon Motives, Inc.
  596. 1 Williston Road, Suite 4
  597. Belmont, MA 02178
  598.  
  599. 617.484.3383
  600. jsa@organon.com
  601.  
  602.